﻿using System;

namespace PC1
{
    public class CommandCoder : ICommandCoder
    {
        public const ulong BcodeMask = 0x00000000F0000000;

        public const int BcodeShift = 28;

        public const int OpcodeShift = 56;

        public const ulong R0Mask = 0x00FF000000000000;

        public const int R0Shift = 48;

        public const ulong R1Mask = 0x0000FF0000000000;

        public const int R1Shift = 40;

        public const ulong R2Mask = 0x000000FF00000000;

        public const int R2Shift = 32;

        public const int SShift = 27;

        public const ulong SMask = 0x0000000008000000;

        public ulong Code(Command command)
        {
            // 6       5  5       4  4       4  3       3
            // 3   7   6  5   6   8  7   5   0  9   4   2
            // 0000 0000  0000 0000  0000 0000  0000 0000
            // 3       2  2       1  1       0  0       0
            // 1   3   4  3   2   6  5   1   8  7   0   0
            // 0000 0000  0000 0000  0000 0000  0000 0000

            var machineCode = ((ulong) command.Opcode) << OpcodeShift;
            switch (command.Opcode)
            {
                case Opcode.HLT:
                    break;
                case Opcode.ADD:
                case Opcode.SUB:
                case Opcode.AND:
                case Opcode.ORR:
                case Opcode.EOR:
                case Opcode.CMP:
                case Opcode.LSL:
                case Opcode.LSR:
                    machineCode |= ((ulong) command.R0) << R0Shift;
                    machineCode |= ((ulong) command.R1) << R1Shift;
                    machineCode |= ((ulong) command.R2) << R2Shift;
                    if(command.Signed) machineCode |= 1 << SShift;
                    break;
                case Opcode.B:
                    machineCode |= ((ulong) command.Bcode) << BcodeShift;
                    break;
                case Opcode.LDR:
                    break;
                case Opcode.STR:
                    break;
                case Opcode.MOV:
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return machineCode;
        }

        public Command Decode(ulong machineCode)
        {
            var i = machineCode >> OpcodeShift;
            var opcode = (Opcode)i;
            var r0 = (byte)((machineCode & R0Mask) >> R0Shift);
            var r1 = (byte)((machineCode & R1Mask) >> R1Shift);
            var r2 = (byte)((machineCode & R2Mask) >> R2Shift);
            i = (machineCode & BcodeMask) >> BcodeShift;
            var s = ((machineCode & SMask) >> SShift) == 1;
            var bcode = (Bcode) i;
            return new Command(opcode, bcode, s, r0, r1, r2);
        }
    }
}